<template>
  <div ref="preview-wrapper" class="preview-wrapper" @mousedown="handleAreaSelect" @mouseup="handleDepressComponent">
    <div
      id="preview-scroller"
      ref="preview-scroller"
      class="preview-scroller absCenterX"
      :style="{ width: previewer.width * scaleRatio + 15 + 'px' }"
    >
      <!-- 预览画布 -->
      <div
        ref="preview-container"
        class="preview-container bgc-white h5"
        style="transform: translateX(-50%) scale(1)"
        @contextmenu.stop.prevent="onContextMenu"
      >
        <!-- header -->
        <div v-if="isH5" class="preview-header d-flex al-end">
          <div
            class="app-name fw-bold t-black"
            :class="{ activated: appNameEdit }"
            :style="{ outlineWidth: `${1 / scaleRatio}px` }"
          ></div>
        </div>
        <div ref="preview-box" class="preview-box">
          <!-- 背景层 -->
          <div class="preview-bg p-a fulled-h fulled-w" :style="backgroundStyle" style="z-index: -1"></div>

          <!-- 拖拽容器 -->
          <vuedraggable
            id="previewer"
            ref="previewer"
            v-model="componentData"
            class="previewer"
            group="Widgets"
            :style="previewerStyle"
            animation="300"
            @sort="onSort"
            @add="onAdd"
            @dragstart.native.stop="handleDragstart"
          >
            <!-- 组件控制层 -->
            <Shape
              v-for="(item, i) in componentData"
              :key="item.id"
              :data-id="item.id"
              :index="i"
              :element="item"
              :origin-style="item.style"
              :style="
                getStyle(item.style, [
                  'position',
                  'width',
                  'top',
                  'left',
                  'paddingTop',
                  'paddingBottom',
                  'paddingLeft',
                  'paddingRight',
                  'zIndex'
                ])
              "
            >
              <!-- 真实组件内容 -->
              <component
                :is="layouts[item.component]"
                :key="item.id"
                :widget="item"
                :index="i"
                class="fulled-w"
                :style="
                  getStyle(item.style, [
                    'color',
                    'fontSize',
                    'lineHeight',
                    'fontWeight',
                    'textDecoration',
                    'textAlign',
                    'opacity',
                    item.flexible ? '' : 'height'
                  ])
                "
              />
            </Shape>
          </vuedraggable>

          <!-- 长度手柄 -->
          <div v-if="isH5" class="handle-bar" @mousedown.stop.prevent="onHandleDown">
            <div v-if="handleBarDown" class="number absCenterX py-5 px-8">{{ previewer.height }}</div>
          </div>

          <!-- 吸附线 -->
          <MarkLine />

          <!-- 右键菜单列表 -->
          <ContextMenu />
          <!-- 框选选区 -->
          <SelectArea v-show="showSelectArea" :style="getStyle(selectArea)" />
        </div>
      </div>
    </div>
    <!-- 右上 控制栏 -->
    <ControlBar ref="controlBar" />
  </div>
</template>

<script>
  import { mapGetters, mapMutations } from 'vuex';
  import '@/plugins/mousewheel';
  import { throttle } from 'lodash-es';
  import vuedraggable from 'vuedraggable';
  import { getStyle, encodePageStyle } from '@/utils';
  import Group from '../Group/Layout';
  import { Basic, H5 } from '../../landingPageWidget/index';
  import Shape from '../previewPanel/Shape';
  import ContextMenu from './ContextMenu';
  import ControlBar from './ControlBar';
  import MarkLine from './MarkLine';
  import SelectArea from './SelectArea';

  let $previewerContainer;

  export default {
    components: {
      vuedraggable,
      Shape,
      ContextMenu,
      ControlBar,
      MarkLine,
      SelectArea
    },

    props: {
      pageConfig: { type: Object, default: () => ({}) },
      applyConfig: { type: Object, default: () => ({}) },
      widgetConfig: { type: Array, default: () => [] }
    },

    data() {
      return {
        appNameEdit: false,
        // 选区大小位置
        showSelectArea: false,
        selectArea: { left: 0, top: 0, width: 0, height: 0 },
        handleBarDown: false
      };
    },

    inject: ['isH5', 'getPageConfigList'],

    computed: {
      ...mapGetters('component', [
        'componentData',
        'activeComponent',
        'isLayeredComponent',
        'componentClick',
        'previewer',
        'multiSelectIndex',
        'zIndexMax',
        'scaleRatio'
      ]),
      pageConfigList() {
        return this.getPageConfigList();
      },
      layouts() {
        return { ...Basic.layouts, ...H5.layouts, Group };
      },
      componentData: {
        get() {
          return this.$store.getters['component/componentData'];
        },
        set(list) {
          this.SET_COMPONENTDATA(list);
          this.RECORD_SNAPSHOT();
          this.$emit('update:widgetConfig', list);
        }
      },
      // 页面背景色
      backgroundStyle() {
        const pageStyle = this.pageConfig.pageStyle;
        const backgroundStyle = encodePageStyle(pageStyle);
        if (pageStyle.fillType === 2 && backgroundStyle.backgroundImage)
          backgroundStyle.backgroundImage = `url(${this.$staticRes(backgroundStyle.backgroundImage)})`;
        return backgroundStyle;
      },
      previewerStyle() {
        return {
          width: `${this.previewer.width}px`,
          height: `${this.previewer.height}px`
        };
      }
    },

    mounted() {
      // 默认宽高、缩放
      this.SET_PREVIEWER(this.pageConfig.size);
      this.$refs.controlBar.onScale(-0.5, 'minus');
      $previewerContainer = this.$refs['preview-container'];
      // 画布自适应resize
      const $previewer = this.$refs.previewer.$el;
      const observeTypes = { childList: true, subtree: true };
      this.previewerObserver = new MutationObserver((mutationList) => {
        const { clientHeight, scrollHeight } = $previewer;
        mutationList.some((mutation) => {
          if (observeTypes[mutation.type] && clientHeight < scrollHeight) {
            // this.pageConfig.size.width = this.pageConfig.size.width;
            this.pageConfig.size.height = scrollHeight + 100;
            return true;
          }
          return false;
        });
      });
      this.isH5 && this.previewerObserver.observe($previewer, observeTypes);
      // 监听编辑AppName
      this.$EventBus.$on('hook:editAppName', (val) => {
        this.appNameEdit = val;
      });
      // 监听键盘快捷操作
      document.addEventListener('keydown', this.handleKeyup);
    },

    beforeDestroy() {
      $previewerContainer = undefined;
      this.previewerObserver.disconnect();
      clearInterval(this.handleDownTimer);
      this.$EventBus.$off('hook:editAppName');
      document.removeEventListener('keydown', this.handleKeyup);
    },

    methods: {
      getStyle,
      ...mapMutations('component', [
        'SHOW_CONTEXTMENU',
        'HIDE_CONTEXTMENU',
        'ACTIVE_COMPONENT',
        'SET_COMPONENTDATA',
        'SET_COMPONENTCLICK',
        'RECORD_SNAPSHOT',
        'SET_PREVIEWER',
        'SET_PREVIEWER_HEIGHT',
        'SET_MULTI_SELECT',
        'MOVE_COMPONENT',
        'DEPRESS_COMPONENT',
        'DELETE_COMPONENT'
      ]),

      // 右键菜单
      onContextMenu(e) {
        this.SET_COMPONENTCLICK(true);
        if (!this.activeComponent && !this.multiSelectIndex.length) return;
        // 计算菜单元素的位置
        let top = e.offsetY;
        let left = e.offsetX;
        const mouseX = e.clientX;
        const mouseY = e.clientY;
        const objX = $previewerContainer.getBoundingClientRect().left - document.documentElement.clientLeft;
        const objY = $previewerContainer.getBoundingClientRect().top - document.documentElement.clientTop;
        top = (mouseY - objY) / this.scaleRatio;
        left = (mouseX - objX) / this.scaleRatio;
        this.SHOW_CONTEXTMENU({ show: true, top, left });
      },

      // 框选选区
      handleAreaSelect(e) {
        // 此处默认当前点击都为取消激活
        this.SET_COMPONENTCLICK(false);
        // 选区起始点
        const $previewer = this.$refs.previewer.$el;
        const { x: previewerX, y: previewerY } = $previewer.getBoundingClientRect();
        const startX = e.clientX - parseInt(previewerX);
        const startY = e.clientY - parseInt(previewerY);
        // 计算范围并回显选区
        this.showSelectArea = true;
        this.selectArea.left = startX;
        this.selectArea.top = startY;
        const move = throttle((e) => {
          const endX = e.clientX - parseInt(previewerX);
          const endY = e.clientY - parseInt(previewerY);
          this.selectArea = {
            left: Math.max(Math.min(startX, endX) / this.scaleRatio, 0),
            top: Math.max(Math.min(startY, endY) / this.scaleRatio, 0),
            width: Math.min(Math.abs(endX - startX) / this.scaleRatio, this.previewer.width),
            height: Math.min(Math.abs(endY - startY) / this.scaleRatio, this.previewer.height)
          };
        });
        // 多选选中范围内组件
        const up = (e) => {
          const multiComponentIndex = [];
          this.componentData.forEach((component, index) => {
            if (!component.isBasic) return;
            const style = this.isLayeredComponent(component) ? component.style : _calcComponentOffset(component);
            const { left, top, width, height } = this.selectArea;
            const right = left + width;
            const bottom = top + height;
            if (style.left < left) return;
            if (style.left + style.width > right) return;
            if (style.top < top) return;
            if (style.top + style.height > bottom) return;
            multiComponentIndex.push(index);
          });
          if (multiComponentIndex.length) this.SET_MULTI_SELECT(multiComponentIndex);
          this.showSelectArea = false;
          this.selectArea = { top: 0, left: 0, width: 0, height: 0 };
          e.currentTarget.removeEventListener('mousemove', move);
        };
        e.currentTarget.addEventListener('mousemove', move);
        e.currentTarget.addEventListener('mouseup', up);
      },

      // 非点击组件事件流：隐藏右键菜单与取消激活组件
      handleDepressComponent() {
        if (this.componentClick) return;
        this.DEPRESS_COMPONENT();
        this.HIDE_CONTEXTMENU();
      },

      // 排序改变时
      onSort(e) {
        const newIndex = e.pullMode === 'clone' ? e.newIndex : e.newDraggableIndex;
        this.ACTIVE_COMPONENT(newIndex);
      },

      // 添加组件时
      onAdd(e) {
        this.$store.state.cloneWidget = false;
        this.ACTIVE_COMPONENT(e.newIndex);
        const component = this.componentData[e.newIndex];
        if (component.style.position === 'absolute') {
          const rectInfo = this.$refs.previewer.$el.getBoundingClientRect();
          const maxY = this.previewer.height - component.style.height;
          const maxX = this.previewer.width - component.style.width;
          const top = (e.originalEvent.clientY - rectInfo.y) / this.scaleRatio - (component.style.height || 0) / 2;
          const left =
            component.style.left ||
            (e.originalEvent.clientX - rectInfo.x) / this.scaleRatio - (component.style.width || 0) / 2;
          component.style.top = top <= 0 ? 0 : top >= maxY ? maxY : top;
          component.style.left = left <= 0 ? 0 : left >= maxX ? maxX : left;
          if (component.origin) component.origin.top = component.style.top;
          if (component.origin) component.origin.left = component.style.left;
        }
        component.style.zIndex = this.zIndexMax + 1;
      },

      // 按键按下时
      handleKeyup: throttle(function (e) {
        if (e.target !== document.body) return;
        if (!this.multiSelectIndex.length) return;
        e.preventDefault();
        switch (e.key) {
          case 'Backspace':
            if (this.activeComponent) this.DELETE_COMPONENT();
            break;
          case 'ArrowUp':
            this.MOVE_COMPONENT({ x: 0, y: -1 });
            break;
          case 'ArrowDown':
            this.MOVE_COMPONENT({ x: 0, y: 1 });
            break;
          case 'ArrowLeft':
            this.MOVE_COMPONENT({ x: -1, y: 0 });
            break;
          case 'ArrowRight':
            this.MOVE_COMPONENT({ x: 1, y: 0 });
            break;
          default:
            return;
        }
      }),

      // 手柄拖动
      onHandleDown(e) {
        // 起始坐标
        let startY = e.pageY;
        const $previewScroller = this.$refs['preview-scroller'];
        const move = throttle((e) => {
          const distanceY = e.pageY - startY;
          this.handleBarDown = true;
          if (
            distanceY < 0 ||
            (distanceY > 0 && $previewScroller.scrollHeight - 280 >= this.previewer.height * this.scaleRatio)
          ) {
            clearInterval(this.handleDownTimer);
            this.handleDownTimer = null;
            if (this.previewer.height + distanceY / this.scaleRatio <= 0) return;
            this.SET_PREVIEWER_HEIGHT(this.previewer.height + distanceY / this.scaleRatio);
            startY = e.pageY;
          } else if (distanceY > 0) {
            if (this.handleDownTimer) return;
            this.handleDownTimer = setInterval(async () => {
              this.SET_PREVIEWER_HEIGHT(this.previewer.height + 2 / this.scaleRatio);
              await this.$nextTick();
              $previewScroller.scrollTop += 2;
            }, 10);
            if (this.previewer.height + distanceY / this.scaleRatio <= 0) return;
            this.SET_PREVIEWER_HEIGHT(this.previewer.height + distanceY / this.scaleRatio);
          }
        });
        const up = () => {
          document.removeEventListener('mousemove', move);
          clearInterval(this.handleDownTimer);
          this.handleDownTimer = null;
          this.handleBarDown = false;
        };
        document.addEventListener('mousemove', move);
        document.addEventListener('mouseup', up);
      },

      handleDragstart(e) {
        e.dataTransfer.setDragImage(document.createElement('div'), 0, 0);
      }
    }
  };

  // 计算非层叠组件的相对画布位置
  function _calcComponentOffset(component) {
    const $component = document.querySelector(`[data-id='${component.id}']`);
    return {
      height: component.style.height,
      width: component.style.width,
      top: $component?.offsetTop || 0,
      left: $component?.offsetLeft || 0
    };
  }
</script>

<style lang="scss" scoped>
  .preview-wrapper {
    position: relative;
    overflow: hidden;
    min-width: 480px;
    background-color: map-get($bgColors, 'light');

    .preview-scroller {
      overflow-x: visible;
      overflow-y: auto;
      margin: 0 auto;
      height: 100%;
    }

    .preview-container {
      position: absolute;
      left: 50%;
      transform-origin: top;
      user-select: none;

      &.h5 {
        top: 130px;
      }

      .preview-header {
        position: absolute;
        top: -176px;
        left: 0;
        z-index: -1;
        padding: 28px 64px;
        width: 750px;
        height: 176px;
        @include bg('marketing/productEdit/bg-preview-header.png');

        .app-name {
          font-size: 36px;
          outline-offset: 6px;

          &.activated {
            outline: 2px dashed map-get($bdColors, 'theme');
          }
        }
      }

      .handle-bar {
        position: absolute;
        bottom: -240px;
        left: 0;
        z-index: 999;
        border-top: 3px solid map-get($bdColors, 'theme');
        width: 100%;
        height: 240px;

        &::after {
          position: absolute;
          top: 0;
          left: 50%;
          display: block;
          border-radius: 0 0 8px 8px;
          width: 48px;
          height: 16px;
          background-color: map-get($bgColors, 'theme');
          transform: translateX(-50%);
          content: '';
          cursor: ns-resize;
        }

        .number {
          top: 50px;
          border-radius: 4px;
          font-size: 24px;
          color: #fff;
          background-color: map-get($bgColors, 'theme');
        }
      }
    }
  }

  .resize-bar {
    position: absolute;
    right: 0;
    bottom: 0;
    border-radius: 6px 0 0 6px;
    width: 248px;
    background-color: #f9f9f9;
    box-shadow: 0 6px 20px 1px rgba(0, 5, 97, 0.04);

    ::v-deep .el-input-number--small {
      width: 72px;
    }
  }

  ::v-deep .sortable-ghost {
    position: relative;
    z-index: 999;
  }
</style>
